using System;
using System.Collections.Generic;
using Activa.LazyParser;
using NUnit.Framework;

namespace UnitTests
{
    [TestFixture]
    public class LazyParserTestFixture
    {
        private readonly ParserContext _context = new CSharpContext();
        private readonly CSharpParser _parser = new CSharpParser();

        private class DataClass
        {
            public DataClass()
            {
            }

            public DataClass(int int1)
            {
                Int1 = int1;
            }


            public DataClass(string string1, int int1)
            {
                String1 = string1;
                Int1 = int1;
            }

            public string String1;
            public int Int1;

            public int Method0() { return 2; }
            public int Method1(int x) { return x * 2; }
            public int Method2(int x, int y) { return x + y; }

            public static int Static1 = 500;
            public static int Static2 { get { return 501; } }

            public int this[int i] { get { return i * 2; } }
            public int this[int x, int y] { get { return x + y; } }

        }

        [SetUp]
        public void SetupFixture()
        {
            DataClass dataObject = new DataClass();

            dataObject.String1 = "blabla1";
            dataObject.Int1 = 123;

            _context.AddType("Math", typeof(Math));
            _context.Set("Data", dataObject);
            _context.AddType("DataClass", typeof(DataClass));
            _context.Set("Func", new Converter<int, int>(Func));
            _context.AddFunction("Max", typeof(Math), "Max");
            _context.AddFunction("fmt", typeof(String), "Format");
            _context.Set("Value10", 10, typeof(int));
            _context.Set("NullableValue5", 5, typeof(int?));
            _context.Set("NullableValueNull", null, typeof(int?));
            _context.Set("MyArray", new int[] { 1, 2, 4, 8, 16 });
            _context.Set("MyArray2", new int[,] { { 1, 2 }, { 2, 4 }, { 4, 8 }, { 8, 16 }, { 16, 32 } });

            _parser.MissingMember += InterceptMissingField;

            _parser.DefaultContext = _context;
        }

        private string _missingField;

        private bool InterceptMissingField(object targetObject, Type targetType, string member, out object fieldValue, out Type fieldType)
        {
            fieldValue = null;
            fieldType = null;

            _missingField = member;

            if (member == "MissingField1")
            {
                if (targetObject == null)
                {
                    fieldValue = 101;
                    fieldType = typeof(int);
                }
                else
                {
                    fieldValue = 100;
                    fieldType = typeof(int);
                }

                return true;
            }

            if (member == "MissingMethod1")
            {
                fieldValue = new Converter<int, int>(delegate(int n) { return n * 2; });
                fieldType = fieldValue.GetType();

                return true;
            }

            return false;
        }

        private static int Func(int i)
        {
            return i * 5;
        }

        [Test]
        public void ComplexExpressions()
        {
            Assert.AreEqual(435, _parser.Evaluate<int>("Math.Max(Data.Method2(Data.Int1+10,300),Data.Method1(Data.Int1))+(\"x\" + 5).Length"));

            Assert.AreEqual(17, _parser.Evaluate<int>("Data.Method2(Data.Method2(3,4),Data.Method1(5))"));
            Assert.AreEqual(100, _parser.Evaluate<int>("Max(Max(100,5),Func(10))"));
            Assert.AreEqual(1000, _parser.Evaluate<int>("Max(Max(100,5),Func(200))"));
        }

        [Test]
        public void StringExpressions()
        {
            Assert.AreEqual("ab", _parser.Evaluate<string>("string.Concat(\"a\",\"b\")"));
            Assert.AreEqual("ab", _parser.Evaluate<string>("\"a\" + \"b\")"));
        }

        [Test]
        public void StringComparisons()
        {
            StringComparison saved = _parser.StringComparison;

            _parser.StringComparison = StringComparison.InvariantCulture;

            Assert.IsTrue(_parser.Evaluate<bool>("\"a\" == \"a\""));
            Assert.IsFalse(_parser.Evaluate<bool>("\"a\" == \"b\""));
            Assert.IsTrue(_parser.Evaluate<bool>("\"a\" != \"b\""));
            Assert.IsFalse(_parser.Evaluate<bool>("\"a\" != \"a\""));

            Assert.IsFalse(_parser.Evaluate<bool>("\"a\" == \"A\""));
            Assert.IsFalse(_parser.Evaluate<bool>("\"a\" == \"B\""));
            Assert.IsTrue(_parser.Evaluate<bool>("\"a\" != \"B\""));
            Assert.IsTrue(_parser.Evaluate<bool>("\"a\" != \"B\""));

            _parser.StringComparison = StringComparison.InvariantCultureIgnoreCase;

            Assert.IsTrue(_parser.Evaluate<bool>("\"a\" == \"A\""));
            Assert.IsFalse(_parser.Evaluate<bool>("\"a\" != \"A\""));

            _parser.StringComparison = saved;
        }


        [Test]
        public void MemberMethods()
        {
            Assert.AreEqual(2, _parser.Evaluate<int>("Data.Method0()"));
            Assert.AreEqual(2, _parser.Evaluate<int>("Math.Max(1,2)"));
            Assert.AreEqual(21, _parser.Evaluate<int>("Data.Method0() + Data.Method1(5) + Data.Method2(5,4)"));
        }

        [Test]
        public void CharLiterals()
        {
            Assert.AreEqual('x', _parser.Evaluate<char>("'x'"));
            Assert.AreEqual('\n', _parser.Evaluate<char>("'\\n'"));
            Assert.AreEqual("Test\n", _parser.Evaluate<string>("\"Test\" + '\\n'"));
            Assert.AreEqual('\'', _parser.Evaluate<char>("'\\''"));
            Assert.AreEqual('\x45', _parser.Evaluate<char>("'\\x45'"));
            Assert.AreEqual('\x4545', _parser.Evaluate<char>("'\\x4545'"));
        }

        [Test]
        public void TypeCast()
        {
            Assert.IsInstanceOfType(typeof(int), _parser.EvaluateToObject("(int)5L"));
        }

        [Test]
        public void StringLiterals()
        {
            Assert.AreEqual("xyz", _parser.Evaluate<string>("\"xyz\""));
            Assert.AreEqual("\n", _parser.Evaluate<string>(@"""\n"""));
            Assert.AreEqual("\f", _parser.Evaluate<string>(@"""\f"""));
            Assert.AreEqual("\"", _parser.Evaluate<string>(@"""\"""""));
            Assert.AreEqual("\x45r\n", _parser.Evaluate<string>(@"""\x45r\n"""));
            Assert.AreEqual("\x45b\n", _parser.Evaluate<string>(@"""\x45b\n"""));
            Assert.AreEqual("\x45bf\n", _parser.Evaluate<string>(@"""\x45bf\n"""));
            Assert.AreEqual("\x45bff\n", _parser.Evaluate<string>(@"""\x45bff\n"""));
        }

        [Test]
        public void NumericLiterals()
        {
            Assert.IsInstanceOfType(typeof(int), _parser.EvaluateToObject("1"));
            Assert.AreEqual(1, _parser.Evaluate<int>("1"));

            Assert.IsInstanceOfType(typeof(long), _parser.EvaluateToObject("10000000000"));
            Assert.AreEqual(10000000000, _parser.Evaluate<long>("10000000000"));

            Assert.IsInstanceOfType(typeof(decimal), _parser.EvaluateToObject("1m"));
            Assert.AreEqual(1m, _parser.Evaluate<decimal>("1m"));

            Assert.IsInstanceOfType(typeof(long), _parser.EvaluateToObject("1L"));
            Assert.AreEqual(1L, _parser.Evaluate<long>("1L"));

            Assert.IsInstanceOfType(typeof(ulong), _parser.EvaluateToObject("1UL"));
            Assert.AreEqual(1UL, _parser.Evaluate<ulong>("1UL"));

            Assert.IsInstanceOfType(typeof(double), _parser.EvaluateToObject("1.0"));
            Assert.AreEqual(1L, _parser.Evaluate<double>("1.0"));

            Assert.IsInstanceOfType(typeof(float), _parser.EvaluateToObject("1.0f"));
            Assert.AreEqual(1L, _parser.Evaluate<float>("1.0f"));

        }

        [Test]
        public void ObjectCreation()
        {
            Assert.IsInstanceOfType(typeof(DataClass), _parser.Evaluate<object>("new DataClass(5)"));

            Assert.AreEqual(5, _parser.Evaluate<int>("(new DataClass(5)).Int1"));
            Assert.AreEqual(5, _parser.Evaluate<int>("new DataClass(5).Int1"));
            Assert.AreEqual(5, _parser.Evaluate<int>("Math.Max(new DataClass(3+2).Int1,3)"));
        }

        [Test]
        public void Delegates()
        {
            Assert.AreEqual(10, _parser.Evaluate<int>("Func(2)"));
            Assert.AreEqual(5, _parser.Evaluate<int>("Max(4,5)"));
        }

        [Test]
        public void Typeof()
        {
            Assert.AreEqual(typeof(int), _parser.Evaluate<Type>("typeof(int)"));
        }

        [Test]
        public void OperatorPrecedence()
        {
            Assert.AreEqual(2, _parser.Evaluate<int>("(5-4)*2"));
            Assert.AreEqual(13, _parser.Evaluate<int>("5+4*2"));
            Assert.AreEqual(22, _parser.Evaluate<int>("5*4+2"));
            Assert.AreEqual(18, _parser.Evaluate<int>("(5+4)*2"));
        }

        [Test]
        public void UnaryNot()
        {
            Assert.AreEqual(false, _parser.Evaluate<bool>("!(1==1)"));
            Assert.AreEqual(true, _parser.Evaluate<bool>("!false"));
            Assert.AreEqual(true, _parser.Evaluate<bool>("!!true"));
        }

        [Test]
        public void UnaryMinus()
        {
            Assert.AreEqual(-2, _parser.Evaluate<int>("-2"));
            Assert.AreEqual(3, _parser.Evaluate<int>("5+-2"));
            Assert.AreEqual(-1, _parser.Evaluate<int>("-(3-2)"));
        }

        [Test]
        public void BitwiseComplement()
        {
            Assert.AreEqual(~2, _parser.Evaluate<int>("~2"));
            Assert.AreEqual(5 + ~2, _parser.Evaluate<int>("5+~2"));
            Assert.AreEqual(~(3 - 2), _parser.Evaluate<int>("~(3 - 2)"));
        }


        [Test]
        public void MissingFieldDelegate()
        {
            Assert.AreEqual(100, _parser.Evaluate<int>("Data.MissingField1"));
            Assert.AreEqual("MissingField1", _missingField);
        }

        [Test]
        public void MissingMethodDelegate()
        {
            Assert.AreEqual(8, _parser.Evaluate<int>("Data.MissingMethod1(4)"));
        }

        [Test]
        public void StaticFields()
        {
            Assert.AreEqual(500, _parser.Evaluate<int>("DataClass.Static1"));
            Assert.AreEqual(501, _parser.Evaluate<int>("DataClass.Static2"));
            Assert.AreEqual(101, _parser.Evaluate<int>("DataClass.MissingField1"));
            Assert.AreEqual(100, _parser.Evaluate<int>("Data.MissingField1"));
        }

        [Test]
        public void NullableLifting()
        {
            Assert.AreEqual(15, _parser.Evaluate<int?>("Value10 + NullableValue5"));
            Assert.IsInstanceOfType(typeof(int?), _parser.Evaluate<int?>("Value10 + NullableValue5"));
            Assert.AreEqual(null, _parser.Evaluate<int?>("Value10 + NullableValueNull"));

        }

        [Test]
        public void Indexing()
        {
            Assert.AreEqual(30, _parser.Evaluate<int>("Data[Func(5),5]"));
        }

        [Test]
        public void ArrayIndexing()
        {
            Assert.AreEqual(8, _parser.Evaluate<int>("MyArray[3]"));
            Assert.AreEqual(8, _parser.Evaluate<int>("MyArray2[2,1]"));

            Assert.AreEqual(16, _parser.Evaluate<int>("MyArray[Data.Method0()+2]"));
            Assert.AreEqual(8, _parser.Evaluate<int>("MyArray2[Data.Method0()+1,0]"));

        }

        [Test]
        public void Ternary()
        {

            Assert.AreEqual(1, _parser.Evaluate<int>("true ? 1:2"));
            Assert.AreEqual(2, _parser.Evaluate<int>("false ? 1:2"));

            _context.Set("a", 1);

            Assert.AreEqual(1, _parser.Evaluate<int>("a==1 ? 1 : 2"));
            Assert.AreEqual(2, _parser.Evaluate<int>("a!=1 ? 1 : 2"));
            Assert.AreEqual("1", _parser.Evaluate<string>("a==1 ? \"1\" : \"2\""));
            Assert.AreEqual("2", _parser.Evaluate<string>("a!=1 ? \"1\" : \"2\""));
            Assert.AreEqual(1, _parser.Evaluate<int>("a==1 ? 1 : a==2 ? 2 : a==3 ? 3 : 4"));

            Assert.AreEqual("x", _parser.Evaluate<string>("a==1 ? \"x\" : a==2 ? \"y\" : a==3 ? \"z\" : \"error\""));
            _context.Set("a", 2);
            Assert.AreEqual("y", _parser.Evaluate<string>("a==1 ? \"x\" : a==2 ? \"y\" : a==3 ? \"z\" : \"error\""));
            _context.Set("a", 3);
            Assert.AreEqual("z", _parser.Evaluate<string>("a==1 ? \"x\" : a==2 ? \"y\" : a==3 ? \"z\" : \"error\""));
            _context.Set("a", 56443);
            Assert.AreEqual("error", _parser.Evaluate<string>("a==1 ? \"x\" : a==2 ? \"y\" : a==3 ? \"z\" : \"error\""));

        }

        [Test]
        public void Comparisons()
        {
            Assert.IsTrue(_parser.Evaluate<bool>("1==1"));
            Assert.IsTrue(_parser.Evaluate<bool>("2>=1"));
            Assert.IsTrue(_parser.Evaluate<bool>("2>1"));
            Assert.IsTrue(_parser.Evaluate<bool>("1<2"));
            Assert.IsFalse(_parser.Evaluate<bool>("2<1"));

            _context.Set("NullString", null, typeof(string));
            _context.Set("ShortValue", 4, typeof(short));

            Assert.IsTrue(_parser.Evaluate<bool>("NullString == null"));
            Assert.IsTrue(_parser.Evaluate<bool>("NullableValue5 == 5"));
            Assert.IsTrue(_parser.Evaluate<bool>("ShortValue == 4"));
        }

        [Test]
        public void AsOperator()
        {
            Assert.AreEqual("x", _parser.Evaluate<string>("\"x\" as string"));
            Assert.AreEqual(null, _parser.Evaluate<string>("5 as string"));

        }

        [Test]
        public void IsOperator()
        {
            Assert.IsTrue(_parser.Evaluate<bool>("\"x\" is string"));
            Assert.IsFalse(_parser.Evaluate<bool>("5 is string"));
            Assert.IsFalse(_parser.Evaluate<bool>("null is string"));

        }

        [Test]
        public void Assignment()
        {
            _context.AssignmentPermissions = AssignmentPermissions.NewVariable;

            Assert.AreEqual(5, _parser.Evaluate<int>("aaa = 5"));

            Assert.IsTrue(_parser.Evaluate<bool>("aaa == 5"));

            _context.AssignmentPermissions = AssignmentPermissions.ExistingVariable;

            Assert.AreEqual(100, _parser.Evaluate<int>("aaa = 100"));
            Assert.IsTrue(_parser.Evaluate<bool>("aaa == 100"));

            _context.AssignmentPermissions = AssignmentPermissions.Variable;

            Assert.AreEqual(200, _parser.Evaluate<int>("aaa = bbb = 100*2"));
            Assert.IsTrue(_parser.Evaluate<bool>("aaa == 200"));
            Assert.IsTrue(_parser.Evaluate<bool>("bbb == 200"));

        }

		[Test]
		[ExpectedException(typeof(IllegalAssignmentException))]
		public void PropertyAssignmentNotAllowed()
		{
			_context.AssignmentPermissions = AssignmentPermissions.None;

			Assert.AreEqual(123, _parser.Evaluate<int>("Data.Int1 = 123"));
		}


        [Test]
        public void PropertyAssignment()
        {
			_context.AssignmentPermissions = AssignmentPermissions.Property;

			Assert.AreEqual(123, _parser.Evaluate<int>("Data.Int1 = 123"));

			Assert.AreEqual(123,_parser.Evaluate<int>("Data.Int1"));
        }


        [Test]
        public void CustomImplicitConversions()
        {
            XElement xEl = new XElement();

            _context.Set("xEl", xEl);

            Assert.AreEqual("attr[Test]", _parser.Evaluate<string>("xEl.Attribute(\"Test\")"));
        }

        [Test]
        public void CustomOperators()
        {
            _context.Set("date1", DateTime.Now);
            _context.Set("date2",DateTime.Now.AddHours(1));

            Assert.IsTrue(_parser.Evaluate<bool>("date1 < date2"));
            Assert.IsFalse(_parser.Evaluate<bool>("date1 > date2"));
            Assert.IsFalse(_parser.Evaluate<bool>("date1 == date2"));
            Assert.AreEqual(1,(int)_parser.Evaluate<TimeSpan>("date2 - date1").TotalHours);

        }

        [Test]
        public void ExpressionTree()
        {
            IParserContext context = new ParserContext();

            ExpressionWithContext expr = new ExpressionWithContext(context);

            expr.Expression = Exp.Add(Exp.Value(4), Exp.Value(5));

            Assert.AreEqual(9, expr.Evaluate().Value);
            Assert.AreEqual(typeof(int), expr.Evaluate().Type);

            expr.Expression = Exp.Add(Exp.Add(Exp.Value(4), Exp.Value((long)5)), Exp.Value(6));

            Assert.AreEqual(15, expr.Evaluate().Value);
            Assert.AreEqual(typeof(long), expr.Evaluate().Type);

            expr.Expression = Exp.Op("<<", Exp.Value((long)4), Exp.Value(2));

            Assert.AreEqual(16, expr.Evaluate().Value);


        }




    }


    public class ExpressionWithContext
    {
        private IParserContext _context;
        private Expression _expr;

        public ExpressionWithContext(IParserContext context)
        {
            Context = context;
        }

        public ExpressionWithContext(IParserContext context, Expression expr)
        {
            Context = context;
            _expr = expr;
        }

        public Expression Expression
        {
            get { return _expr; }
            set { _expr = value; }
        }

        public IParserContext Context
        {
            get { return _context; }
            set { _context = value; }
        }

        public ValueExpression Evaluate()
        {
            return Expression.Evaluate(Context);
        }
    }

    public class XElement
    {
        public string Attribute(XName xName)
        {
            return "attr[" + xName + "]";
        }
    }

    public class XName
    {
        private string _name;

        public static implicit operator XName(string s)
        {
            XName xName = new XName();

            xName._name = s;

            return xName;
        }

        public override string ToString()
        {
            return _name;
        }
    }

}
